package com.anc.platform.utils;

import exception.AncException;
import lombok.SneakyThrows;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * 通过方法引用获取属性名(参考自mybatis-plus)
 * 引用自:https://blog.csdn.net/qq_39809458/article/details/101423610
 *
 * @author caoyuan
 * @date 2021/7/15 上午 9:36
 */
public interface TypeFunction<T, R> extends Serializable, Function<T, R> {

    Map<Class<?>, SerializedLambda> CLASS_LAMDBA_CACHE = new ConcurrentHashMap<>();

    /***
     * 获取lambda指向的get方法对应的属性名
     */
    static <T> String getFieldName(TypeFunction<T, ?> fn) {
        SerializedLambda lambda = getSerializedLambda(fn);
        // 获取方法名
        String methodName = lambda.getImplMethodName();
        if (methodName.startsWith("get")) {
            methodName = methodName.substring(3);
        } else if (methodName.startsWith("is")) {
            methodName = methodName.substring(2);
        } else {
            throw new AncException("无效的getter方法: " + methodName);
        }
        // 截取get/is之后的字符串并转换首字母为小写
        return StringUtils.uncapitalize(methodName);
    }

    /**
     * 获取lambda指向的类
     */
    @SneakyThrows//不处理异常
    static <T> Class<T> getClass(TypeFunction<T, ?> fn) {
        SerializedLambda lambda = getSerializedLambda(fn);
        String implClass = lambda.getImplClass();
        return (Class<T>) Class.forName(implClass.replace("/", "."));
    }

    /**
     * 关键在于这个方法
     */
    @SneakyThrows//不处理异常
    static SerializedLambda getSerializedLambda(Serializable fn) {
        SerializedLambda lambda = CLASS_LAMDBA_CACHE.get(fn.getClass());
        // 先检查缓存中是否已存在
        if (lambda == null) {
            // 提取SerializedLambda并缓存
            Method method = fn.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            lambda = (SerializedLambda) method.invoke(fn);
            CLASS_LAMDBA_CACHE.put(fn.getClass(), lambda);
        }
        return lambda;
    }
}